/*
 * GameMap.java
 * 
 * author: The MomberBan Team
 *    date: 12.09.2008
 * 
 */

package at.momberban2.me.gamecontext;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;

import at.momberban2.me.game.M2Event;
import at.momberban2.me.game.M2ResourceManager;
import at.syncme.framework.Entity;
import at.syncme.framework.legacy.WrapperCache;
import de.enough.polish.util.HashMap;
import de.enough.polish.util.Map;
import de.enough.polish.util.MathUtil;
import de.enough.polish.util.Properties;

/**
 * a game map in momberban. holds all necessary layers to do collision
 * detection.
 */
public class M2Map extends Entity {
    private static final M2ResourceManager MGR = (M2ResourceManager) M2ResourceManager
            .getInstance();

    private static final String TOKEN_SIZE_X = "size_x";

    private static final String TOKEN_SIZE_Y = "size_y";

    private static final String TOKEN_TITLE = "title";

    private static final String TOKEN_PLAYERS = "players";

    private static final String TOKEN_FREE_SPACE = "freespace";

    private static final String TOKEN_UNBREAKABLE = "unbreakable";

    private static final String TOKEN_BREAKABLE = "breakable";

    private static final String ROW_PREFIX = "r";

    private static final char FREE_SPACE = '.';

    private static final char WALL_UNBREAKABLE = 'u';

    private static final char WALL_BREAKABLE = 'b';

    private TiledLayer backgroundLayer, breakableLayer, unbreakableLayer,
            itemLayer;

    private short width, height, xTiles, yTiles, tileWidth, tileHeight;

    private byte maxPlayers, defaultMaxPlayers;

    private String title;

    private String fileName;

    private Map spawns;

    /**
     * default constructor
     */
    public M2Map() {
        super();
    }

    /**
     * constructor
     * 
     * @param title
     *            the title
     * @param fileName
     *            the resource to load
     */
    public M2Map(String title, String fileName) {
        this.fileName = fileName;
        this.title = title;
    }

    /**
     * read the map
     * 
     * @throws IOException
     */
    public void reset() {
        // read the whole map file
        try {
            final Properties prop = new Properties(this.fileName);

            // general
            this.xTiles = Short.parseShort(prop.getProperty(TOKEN_SIZE_X));
            this.yTiles = Short.parseShort(prop.getProperty(TOKEN_SIZE_Y));
            this.maxPlayers = Byte.parseByte(prop.getProperty(TOKEN_PLAYERS));
            this.title = prop.getProperty(TOKEN_TITLE);

            // layers
            Image img = MGR.loadImage(prop.getProperty(TOKEN_FREE_SPACE));
            this.backgroundLayer = new TiledLayer(1, 1, img, img.getWidth(),
                    img.getHeight());
            this.backgroundLayer.setCell(0, 0, 1);

            img = MGR.loadImage(prop.getProperty(TOKEN_BREAKABLE));
            this.breakableLayer = new TiledLayer(this.xTiles, this.yTiles, img,
                    img.getWidth(), img.getHeight());

            img = MGR.loadImage(prop.getProperty(TOKEN_UNBREAKABLE));
            this.unbreakableLayer = new TiledLayer(this.xTiles, this.yTiles,
                    img, img.getWidth(), img.getHeight());

            // scan the map
            this.spawns = new HashMap();
            for (int i = 0; i < this.yTiles; i++) {
                String row = prop.getProperty(ROW_PREFIX + i);
                for (int j = 0; j < this.xTiles; j++) {
                    switch (row.charAt(j)) {
                        case WALL_BREAKABLE:
                            this.breakableLayer.setCell(j, i, 1);
                            this.unbreakableLayer.setCell(j, i, 0);
                            break;
                        case WALL_UNBREAKABLE:
                            this.breakableLayer.setCell(j, i, 0);
                            this.unbreakableLayer.setCell(j, i, 1);
                            break;
                        case FREE_SPACE:
                            this.breakableLayer.setCell(j, i, 0);
                            this.unbreakableLayer.setCell(j, i, 0);
                            break;
                        default:
                            Integer playerId = Integer.valueOf(row.charAt(j)
                                    + "");
                            int[] pos = new int[2];
                            pos[0] = j * this.tileWidth;
                            pos[1] = i * this.tileHeight;

                            this.spawns.put(playerId, pos);
                    }
                }
            }

            // calculated
            this.width = (short) this.backgroundLayer.getWidth();
            this.height = (short) this.backgroundLayer.getHeight();
            this.tileWidth = (short) (this.width / this.xTiles);
            this.tileHeight = (short) (this.width / this.xTiles);

        } catch (IOException e) {
            System.out.println("could not load map " + this.fileName);
            System.out.println(e.getMessage());
        }

        // TODO: fixme
        // this.itemLayer = new TiledLayer(this.xTiles, this.yTiles, MGR
        // .loadImage("/images/Bomb.png"), this.tileWidth, this.tileHeight);
        // this.itemLayer.setCell(5, 0, 1);

        // int animatedTile = this.bombLayer.createAnimatedTile(3);
        // this.bombLayer.setCell(3, 0, animatedTile);
    }

    /**
     * @return title
     */
    public String getTitle() {
        return title;
    }

    /**
     * Getter of the property <tt>maxPlayers</tt>
     * 
     * @return Returns the maxPlayers.
     */

    public byte getMaxPlayers() {
        return maxPlayers;
    }

    /**
     * Setter of the property <tt>maxPlayers</tt>
     * 
     * @param maxPlayers
     *            The maxPlayers to set.
     */
    public void setMaxPlayers(byte maxPlayers) {
        this.maxPlayers = maxPlayers;
    }

    /**
     * @return backgroundLayer
     */
    public TiledLayer getBackgroundLayer() {
        return backgroundLayer;
    }

    /**
     * @return breakableLayer
     */
    public TiledLayer getBreakableLayer() {
        return breakableLayer;
    }

    /**
     * @return unbreakableLayer
     */
    public TiledLayer getUnbreakableLayer() {
        return unbreakableLayer;
    }

    /**
     * @return bombLayer
     */
    public TiledLayer getItemLayer() {
        return itemLayer;
    }

    /**
     * return the spawn for a player
     * 
     * @param id
     * @return the spawn int[2]
     */
    public int[] getSpawn(int id) {
        return (int[]) this.spawns.get(WrapperCache.valueOf(id));
    }

    /**
     * @return width
     */
    public int getWidth() {
        return width;
    }

    /**
     * @return height
     */
    public int getHeight() {
        return height;
    }

    /**
     * @return xTiles
     */
    public int getXTiles() {
        return xTiles;
    }

    /**
     * @return yTiles
     */
    public int getYTiles() {
        return yTiles;
    }

    /**
     * @return tileWidth
     */
    public int getTileWidth() {
        return tileWidth;
    }

    /**
     * @return tileHeight
     */
    public int getTileHeight() {
        return tileHeight;
    }

    /**
     * @return defaultMaxPlayers
     */
    public int getDefaultMaxPlayers() {
        return defaultMaxPlayers;
    }

    /**
     * collision check with walls and outer world
     * 
     * @param s
     * @param newX
     * @param newY
     * @return true, if valid
     */
    public boolean isCollided(Sprite s) {
        int posX = s.getX();
        int posY = s.getY();

        if (isValidPosition(posX, posY)) {
            return s.collidesWith(this.breakableLayer, true)
                    || s.collidesWith(this.unbreakableLayer, true);
        } else {
            return true;
        }
    }

    /**
     * @param newX
     * @param newY
     * @return true if valid
     */
    private boolean isValidPosition(int newX, int newY) {
        return newX >= 0 && newX <= this.width - M2Player.PLAYER_WIDTH
                && newY >= 0 && newY <= this.height - M2Player.PLAYER_HEIGHT;
    }

    /**
     * spawn a player on it's default spawn on the map
     * 
     * @param player
     */
    public void spawnPlayer(M2Player player) {
        Sprite s = player.getSprite();
        int[] spawn = getSpawn(player.getId());
        s.setPosition(spawn[0], spawn[1]);
    }

    /**
     * return the tile info for the target <code>
     * tile w = 20, 100 total line, what is the tile id then?
     *****************************
     *      20 20 20 20 20 * [100]
     *****************************
     * 20 * 0  1  2  3  4  *
     * 20 * 5  6  7  8  9 *
     * 20 * 10 11 12 13 14 *
     * 20 * 15 16 17 18 19 *
     * *********************
     * [80]
     * </code>
     * 
     * @param pos
     *            ascending position number
     * @param spriteWidth
     *            the sprite width
     * @return an array of three elements [id, x, y]
     */
    public final M2TileInfo decodePosition(int pos, int spriteWidth) {
        int colId = pos % this.xTiles;
        int rowId = pos / this.xTiles;

        int offsetX = ((this.width / this.xTiles) - spriteWidth) / 2;

        return getTileInfo(colId, rowId, offsetX);
    }

    /**
     * get the target tile info on the map. check the tile, the player is within
     * and assign a id for that position [0]. the other two parts are the new
     * coordinates
     * 
     * @param x
     *            current x
     * @param y
     *            current y
     * @param spriteWidth
     *            the sprite width
     * @return an array of three elements [id, x, y]
     */
    public final M2TileInfo decodePosition(int x, int y, int spriteWidth) {
        // the x offset is used to compensate the sprite width with the tile
        int offsetX = ((this.width / this.xTiles) - spriteWidth) / 2;

        int colId = MathUtil.round((float) (x - offsetX) * this.xTiles
                / this.width);
        int rowId = MathUtil.round((float) y * this.yTiles / this.height);

        return getTileInfo(colId, rowId, offsetX);
    }

    /**
     * build some info for a tile
     * 
     * @param colId
     * @param rowId
     * @return
     */
    private final M2TileInfo getTileInfo(int colId, int rowId, int offsetX) {
        M2TileInfo ret = new M2TileInfo();

        ret.setId(colId + rowId * this.xTiles);
        ret.setX(colId * this.width / this.xTiles + offsetX);
        ret.setY(rowId * this.height / this.yTiles);

        // build the tile status
        if (this.breakableLayer.getCell(colId, rowId) == 1) {
            ret.setState(M2TileInfo.TILE_BREAKABLE);
        } else if (this.unbreakableLayer.getCell(colId, rowId) == 1) {
            ret.setState(M2TileInfo.TILE_UNBREAKABLE);
            // } else if (this.game.getBombs().get(WrapperCache.valueOf(ret[0]))
            // != null) {
            // ret[3] = TILE_OCCUPIED;
        } else {
            ret.setState(M2TileInfo.TILE_FREE);
        }

        return ret;
    }

    /**
     * override
     * 
     * @see at.syncme.framework.Entity#read(java.io.DataInputStream)
     */
    public void read(DataInputStream in) throws IOException {
        // first load static part
        this.fileName = in.readUTF();
        reset();

        // read breakable wall condition (skip if zero metrics are read)
        int dynX = in.readShort();
        int dynY = in.readShort();
        for (int i = 0; i < dynY; i++) {
            for (int j = 0; j < dynX; j++) {
                byte condition = in.readByte();
                System.out.println(j + "," + i + "," + condition);
                this.breakableLayer.setCell(j, i, condition);
            }
        }
    }

    /**
     * override
     * 
     * @see at.syncme.framework.Entity#write(java.io.DataOutputStream)
     */
    public void write(DataOutputStream out) throws IOException {
        // write filename (static part)
        out.writeUTF(this.fileName);

        // write breakable wall (can write zero metrics too)
        out.writeShort(this.xTiles);
        out.writeShort(this.yTiles);
        for (short i = 0; i < this.yTiles; i++) {
            for (short j = 0; j < this.xTiles; j++) {
                byte condition = (byte) this.breakableLayer.getCell(j, i);
                System.out.println(j + "," + i + "," + condition);
                out.writeByte((byte) this.breakableLayer.getCell(j, i));
            }
        }
    }

    /**
     * override
     * 
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return "map: " + this.fileName + ", " + this.xTiles + "x" + this.yTiles;
    }
}
